perf(core): reuse written note content after writes#717
Conversation
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 1m 45s —— View job PR Review: perf(core): reuse written note content after writes
SummaryThis PR eliminates two redundant post-write file reads from the non-fast create/update/edit paths by introducing a new The approach is clean and well-scoped. Checklist results below. Code Quality & Standards
Testing & Documentation
Architecture & Performance
Specific Code Observations
# Before
async def detect_file_path_conflicts(self, file_path: str, skip_check: bool = False) -> List[Entity]:
# After
async def detect_file_path_conflicts(self, file_path: str, skip_check: bool = False) -> List[str]:The old return type was
write_result = await entity_service.create_entity_with_content(data)
entity = write_result.entity
written_content = write_result.content
if entity.external_id != entity_id:
entity = await entity_repository.update(entity.id, {"external_id": entity_id})
# written_content is NOT updated here
class FakeFileService:
async def read_file_content(self, path):
raise AssertionError("non-fast create should not re-read file content")This is an excellent pattern for verifying the optimization is upheld by future changes. Well done. Minor Suggestions (non-blocking)
Checklist Results
Overall: LGTM. The optimization is correct, well-tested, and the delegation pattern keeps the existing API surface stable. The one minor suggestion (inline comment for the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f853e61c95
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| phase="search_index", | ||
| ): | ||
| await search_service.index_entity(entity) | ||
| await search_service.index_entity(entity, content=written_content) |
There was a problem hiding this comment.
Keep search indexing on normalized note content
The non-fast create path now calls search_service.index_entity(entity, content=written_content), but written_content is full serialized markdown from dump_frontmatter(...) in create_entity_with_content, while the previous path indexed read_entity_content(...) output (content normalized by the markdown processor). This change makes YAML/frontmatter tokens part of indexed content, which can materially degrade search relevance by injecting metadata noise into every note; pass normalized body content (or keep the existing read/normalize path) before indexing.
Useful? React with 👍 / 👎.
| if fast: | ||
| content = await file_service.read_file_content(entity.file_path) | ||
| else: | ||
| content = written_content |
There was a problem hiding this comment.
Return persisted content after formatter rewrites
For non-fast writes, the response content now comes from in-memory written_content instead of reading the saved file. FileService.write_file can rewrite the file when format_on_save is enabled, so this branch can return pre-format text that no longer matches what was persisted, causing API clients to observe stale content immediately after a successful write; use the persisted file content (or formatter output) for the response body in this branch.
Useful? React with 👍 / 👎.
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 2m 46s —— View job PR Review: perf(core): reuse written note content after writes
SummaryThis PR eliminates two redundant post-write file reads from the non-fast create/update/edit paths by introducing The approach is well-scoped and follows existing patterns. Two issues from the Codex review deserve a closer look; assessments are below. Code Quality & Standards
Testing & Documentation
Architecture & Performance
Codex Review CommentsComment 1 — Codex flags that Tracing through if content is None:
content = await self.file_service.read_entity_content(entity) # body only
if content:
content_stems.append(content)
content_snippet = _strip_nul(content)When Verdict: valid but low severity. Frontmatter tokens are largely benign (they overlap with what users search for) and the fast path's background # in create_entity_with_content / update_entity_with_content
from basic_memory.file_utils import remove_frontmatter
return EntityWriteResult(entity=updated, content=remove_frontmatter(final_content))Not blocking, but flagging for awareness. Fix this → Comment 2 — Codex notes that This is technically correct. The pre-PR code read back from file after writing, so it would have returned the post-formatter content. If Not blocking given the default-off nature of the feature. Fix this → Minor Observations
Checklist Results
Overall: LGTM with two non-blocking notes. The core optimization is correct and well-tested. The Codex comments are valid observations: (1) search indexing now receives full markdown including frontmatter for non-fast writes, which is a behavioral delta from the pre-PR body-only indexing; (2) |
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 2m 13s —— View job PR Review: perf(core): reuse written note content after writes
SummaryThis PR introduces Code Quality & Standards
Testing & Documentation
Architecture & Performance
Codex Review CommentsComment 1 — Confirmed valid. In
The title and permalink are also indexed separately via Verdict: low practical impact today (non-fast path is the minority path, and FTS overlap is benign for most terms), but worth resolving. The clean fix is to strip frontmatter before constructing # entity_service.py — in create_entity_with_content, update_entity_with_content, edit_entity_with_content
return EntityWriteResult(entity=updated, content=remove_frontmatter(final_content))
Comment 2 — Confirmed valid but very low practical impact. In The fix above ( Checklist Results
Overall: LGTM with one actionable suggestion. The core optimization is correct and well-tested. The most concrete improvement is using |
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 1m 21s —— View job PR Review: perf(core): reuse written note content after writes
SummaryThis revision ( Status of Prior Review Items
Open Item: Frontmatter in Search Index
# entity_service.py:337
return EntityWriteResult(entity=updated, content=final_content)
# entity_service.py:462
return EntityWriteResult(entity=entity, content=final_content)
# entity_service.py:1096
return EntityWriteResult(entity=entity, content=new_content)
return EntityWriteResult(entity=updated, content=remove_frontmatter(final_content))This would restore exact parity with the pre-PR behavior where Checklist Results
Overall: LGTM pending the |
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 1m 13s —— View job PR Review: perf(core): reuse written note content after writes
SummaryCommit
Status of Prior Review Items
Code QualityThe fix is well-structured:
Checklist Results
Overall: LGTM. All prior P2 items are resolved. The two-field design is the right call — it makes the intent explicit at the data layer rather than stripping frontmatter in the router, keeps the response body as full markdown, and gives search indexing exactly what it needs. No further changes needed. |
Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude finished @phernandez's task in 1m 38s —— View job PR Review: perf(core): reuse written note content after writes
SummaryThis PR introduces Code Quality & Standards
Testing & Documentation
Architecture & Performance
Resolved P2 Items from Prior Reviews
Checklist Results
Overall: LGTM. The two-field design is the right call — it makes intent explicit at the data layer, keeps the response body as full markdown, and gives FTS exactly what it needs (body-only). All prior P2 items are resolved and the |
Summary
Root cause
write_noteandedit_notecurrently use the non-fast knowledge API path. After writing a note, that path was re-reading the same file from storage for search indexing and again for the response body, which matches the redundant S3 round-trips called out in issue #708.Impact
This removes two redundant post-write reads from the non-fast create/update/edit path, which should reduce latency for cloud-backed note writes and edits. The permalink follow-up in issue #708 still has room for deeper optimization, but the duplicate read path is addressed here.
Validation
uv run pytest -q tests/api/v2/test_knowledge_router.py tests/api/v2/test_knowledge_router_telemetry.py tests/services/test_entity_service_telemetry.pyuv run ruff check src/basic_memory/services/entity_service.py src/basic_memory/api/v2/routers/knowledge_router.py tests/api/v2/test_knowledge_router.py tests/api/v2/test_knowledge_router_telemetry.pyuv run pyright src/basic_memory/services/entity_service.py src/basic_memory/api/v2/routers/knowledge_router.pyCloses #708